package main

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"io"
	"net"
	"os"
	"runtime"
	"strconv"
)

const (
	MAX_BUFFER_SIZE = 1024 * 1024
	REQ_HEADER      = "10001"
)

func check_error(err error) {
	if err != nil {
		fmt.Fprintf(os.Stderr, "fatal error: %s\n", err.Error())
		os.Exit(1)
	}
}

func Int64ToBytes(i int64) []byte {
	var buf = make([]byte, 8) // int64 is 8 byte
	binary.LittleEndian.PutUint64(buf, uint64(i))
	return buf
}

func BytesToInt64(buf []byte) int64 {
	return int64(binary.LittleEndian.Uint64(buf))
}

func writeFile(file string, offset int64, s_real_req string) {

	fout, err := os.OpenFile(file, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
	check_error(err)
	defer fout.Close()

	var bytes_buf bytes.Buffer
	bytes_buf.WriteString(s_real_req)

	var write_len int = len(s_real_req)
	cnt, err := fout.WriteAt(bytes_buf.Bytes(), offset)
	if cnt != write_len {
		fmt.Fprintf(os.Stderr, "WriteAt: offset[%d] cnt[%d] !=  write_len[%d] err: %s\n",
			offset, cnt, write_len, err.Error())
		return
	}
	//fmt.Printf("WriteAt: offset[%d] bytes[%d] s_real_req[%#v]\n", offset, cnt, s_real_req)
	fmt.Printf("WriteAt: offset[%d] bytes[%d]\n", offset, cnt)
}

func handleClient(conn net.Conn, file string) {
	defer conn.Close()

	var buf [MAX_BUFFER_SIZE]byte
	var has_read_cnt int = 0
	var idx int = 0

	for {
		n, err := conn.Read(buf[idx:])
		if err != nil {
			if err != io.EOF {
				fmt.Fprintf(os.Stderr, "Read: %s\n", err.Error())
			}
			return
		}
		has_read_cnt += n
		//fmt.Printf("handleClient: len[%d] req[%#v]\n", n, buf[0:n])
		fmt.Printf("handleClient: len[%d]\n", has_read_cnt)

		// reqlen_total + header + offset + req
		if has_read_cnt < 8 {
			fmt.Fprintf(os.Stderr, "invalid req len, drop it\n")
			return
		}

		var bytes_buf bytes.Buffer
		bytes_buf.Write(buf[0:has_read_cnt])

		reqlen_total := BytesToInt64(bytes_buf.Next(8))
		if reqlen_total != int64(has_read_cnt) {
			fmt.Fprintf(os.Stderr, "req not get complete, reqlen_total[%d] != has_read_cnt[%d]\n",
				reqlen_total, has_read_cnt)

			idx += has_read_cnt
			continue
		}

		header := BytesToInt64(bytes_buf.Next(8))
		req_header, _ := strconv.ParseInt(REQ_HEADER, 10, 64)

		if header != req_header {
			fmt.Fprintf(os.Stderr, "invalid req header, drop it\n")
			return
		}

		offset := BytesToInt64(bytes_buf.Next(8))
		var s_real_req string = string(bytes_buf.Bytes())
		//fmt.Printf("file[%s] offset[%d] s_real_req[%#v]\n", file, offset, s_real_req)
		fmt.Printf("handleClient: file[%s] offset[%d]\n", file, offset)

		writeFile(file, offset, s_real_req)

		if n > 0 {
			_, err = conn.Write([]byte("ok"))
			if err != nil {
				fmt.Fprintf(os.Stderr, "Write: err[%s]\n", err.Error())
				return
			}
		}
	}
}

func file_rcv_server(addr string) {
	runtime.GOMAXPROCS(runtime.NumCPU())

	tcp_addr, err := net.ResolveTCPAddr("tcp4", addr)
	check_error(err)

	listener, err := net.ListenTCP("tcp", tcp_addr)
	check_error(err)

	for {
		conn, err := listener.Accept()
		if err != nil {
			fmt.Fprintf(os.Stderr, "Accept: err[%s]\n", err.Error())
			continue
		}
		go handleClient(conn)
	}
}

func main() {
	file_rcv_server("127.0.0.1:9001")
}
